﻿Imports System.IO.Ports
Imports System.Windows.Forms

Public Class clsRfidTag

    Public Enum Mode
        Command = &H0
        AutoScan = &H1
        Trigger = &H2
        Polling = &H3
        EAS = &H24
        Continual = &H50
        RDLOOP = &H58
        RDLOOPcmd = &H59
    End Enum

    Public Enum WriteFlow
        SendCmd
        WaitForRsp
        WaitForData
        Done
    End Enum

    Public Enum WriteError
        OK
        TagReaderFailed
        NoTag
        TimeOut
        NACK
    End Enum

    Private WriteCond As WriteFlow

    WithEvents TimerWrite As New Timer

    Public Const ETX As Byte = &H3
    Public Const CR As Byte = &HD
    Public Const MIN_BYTE As Integer = 3
    Public Const UID_BIGIN As Integer = 11
    Public Const UID_END As Integer = 4
    Public Const UID_LENGTH = UID_BIGIN - UID_END + 1

    'Public WithEvents SerialPort1 As System.IO.Ports.SerialPort
    Private serialHandler As SerialDataReceivedEventHandler

    Public Delegate Sub Del_DataReciev()
    Private DataRecive As Del_DataReciev

    Private byteRecv As New List(Of Byte())
    Private byteUid As New List(Of Byte())

    Private fRespOK As Boolean = False
    Private fDataOK As Boolean = False
    Private fIsRDLOOP As Boolean = True

    Public WriteOnly Property ResponseOK As Boolean
        Set(ByVal value As Boolean)
            fRespOK = value
        End Set
    End Property

    Public WriteOnly Property DataOK As Boolean
        Set(ByVal value As Boolean)
            fDataOK = value
        End Set
    End Property

    Public Sub BeginSerial(ByVal szPortName As String, ByVal dataRecive As Del_DataReciev, Optional ByVal fShowMsg As Boolean = True)
        If IsNothing(g_SerialPort) Then
            g_SerialPort = New SerialPort(szPortName, 9600, Parity.Even, 7, StopBits.One)
        End If

        Me.DataRecive = dataRecive
        If Not g_SerialPort.IsOpen Then
            Try
                g_SerialPort.ReadBufferSize = 256
                g_SerialPort.Open()
                'g_SerialPort serialPort.DataReceived, AddressOf DataReceive
            Catch ex As Exception
                If fShowMsg Then
                    MsgBox(ex.Message)
                End If

                Throw ex
            End Try
        End If
    End Sub

    Protected Overrides Sub Finalize()
        If IsNothing(g_SerialPort) Then
            Exit Sub
        End If
        Disconnect()
        RemoveHandler g_SerialPort.DataReceived, serialHandler
        g_SerialPort.Dispose()
        'SerialPort1.Dispose()
        g_SerialPort = Nothing
        'SerialPort1 = Nothing
    End Sub

    Public Sub EndSerial()
        Finalize()
    End Sub

    Public Sub AddSerialEventHandler(ByVal serialHandler As SerialDataReceivedEventHandler)
        Me.serialHandler = serialHandler
        AddHandler g_SerialPort.DataReceived, Me.serialHandler
        g_SerialPort = g_SerialPort
        If Not g_SerialPort.IsOpen Then
            g_SerialPort.Open()
        End If
    End Sub

    Public ReadOnly Property ReceivedData(Optional ByVal i As Integer = 0) As Byte()
        Get
            If byteRecv.Count > 0 Then
                ReceivedData = byteRecv(i)
            Else
                ReceivedData = Nothing
            End If
        End Get
    End Property

    Public ReadOnly Property UID(Optional ByVal i As Integer = 0) As Byte()
        Get
            If byteRecv.Count > 0 Then
                UID = byteUid(i)
            Else
                UID = Nothing
            End If
        End Get
    End Property

    Public ReadOnly Property DataCount As Integer
        Get
            DataCount = byteRecv.Count
        End Get
    End Property

    Public ReadOnly Property IsOpen As Boolean
        Get
            IsOpen = g_SerialPort.IsOpen
        End Get
    End Property

    Public ReadOnly Property SerialPort As IO.Ports.SerialPort
        Get
            SerialPort = g_SerialPort
        End Get
    End Property

    'Public Sub AddSerialEventHandler()
    '    'AddHandler serialPort.DataReceived, AddressOf DataReceive
    'End Sub

    Public Sub Connect()
        If Not g_SerialPort.IsOpen Then
            g_SerialPort.Open()
        End If
    End Sub

    Public Sub ClearData()
        byteRecv = New List(Of Byte())
        byteUid = New List(Of Byte())
        Try
            g_SerialPort.DiscardInBuffer()
            g_SerialPort.DiscardOutBuffer()
        Catch
        End Try
    End Sub

    Public Sub DisableHandShake()
        If Not IsNothing(g_SerialPort) Then
            g_SerialPort.DtrEnable = False
            g_SerialPort.RtsEnable = False
        End If
    End Sub

    Public Sub Disconnect()
        If IsNothing(g_SerialPort) Then
            Exit Sub
        End If
        If g_SerialPort.IsOpen Then
            g_SerialPort.Close()
        End If
    End Sub

    Public Function SetMod(ByVal mode As Mode, Optional ByVal fMultiTags As Boolean = False, Optional ByVal fBeep As Boolean = True) As Boolean
        Dim DataSend(4) As Byte
        DataSend(0) = &H0 '00H:RAMへの書き込み、10H:EEPROMへの書き込み
        DataSend(1) = CByte(mode)
        DataSend(2) = &H0
        'DataSend(3) = &H30 'UIDを含む
        'DataSend(3) = &H10 'UIDを含まない
        DataSend(3) = &H0
        If fMultiTags Then
            DataSend(3) += &H24 'UIDを含む、アンチコリジョン有効（複数枚のICタッグには必須）
            'Else
            '    DataSend(3) = &H30 'UIDを含む
        End If
        If fBeep Then
            DataSend(3) += &H10
        End If
        Dim rslt As WriteError = SendCommand(&H4E, DataSend)
        If rslt = WriteError.OK Then
            SetMod = True
        Else
            SetMod = False
        End If
        DataSend(4) = 0

        'Modeを記録する
        If mode = clsRfidTag.Mode.Command Then
            fIsRDLOOP = False
        ElseIf mode = clsRfidTag.Mode.RDLOOP Then
            fIsRDLOOP = True
        End If
    End Function

    Public Function ReadMode() As Mode
        Dim dataSend(0) As Byte
        dataSend(0) = &H0

        SendCommand(&H4F, dataSend)

        ReadMode = CType(byteRecv(byteRecv.Count - 1)(5), Mode)
    End Function

    Public Sub WriteData(ByVal chrData() As Char)
        Dim dataSend() As Byte = New Byte(chrData.Length + 3) {}
        Dim i As Integer
        dataSend(0) = &HA1
        dataSend(1) = &H0
        dataSend(2) = chrData.Length
        For i = 0 To chrData.Length - 1
            dataSend(i + 3) = AscW(chrData(i))
        Next
        'dataSend(i + 3) = &H40 '

        dataSend(i + 3) = &H5 '

        SetMod(Mode.Command, True)

        ResetToReady()

        SendCommand(&H78, dataSend)

        SetMod(Mode.RDLOOP, True)

        'WaitToWrite()

    End Sub

    Public Sub WriteData(ByVal byteData() As Byte)
        Dim dataSend() As Byte = New Byte(byteData.Length + 3) {}
        Dim i As Integer
        dataSend(0) = &HA1
        dataSend(1) = &H0
        dataSend(2) = byteData.Length
        For i = 0 To byteData.Length - 1
            dataSend(i + 3) = byteData(i)
        Next
        dataSend(i + 3) = &H40

        SetMod(Mode.Command)

        ResetToReady()

        SendCommand(&H78, dataSend)

        SetMod(Mode.RDLOOP)

        'WaitToWrite()
    End Sub

    Public Function WriteData(ByVal byteData() As Byte, ByVal szUID As String) As WriteError
        Dim dataSend() As Byte = New Byte(byteData.Length + 3 + UID_LENGTH) {}
        Dim i As Integer
        dataSend(0) = &HA1
        dataSend(1) = &H0
        dataSend(2) = byteData.Length
        For i = 0 To byteData.Length - 1
            dataSend(i + 3) = byteData(i)
        Next
        dataSend(i + 3) = &H1
        i += 3

        Dim szUidArray() As String
        If Not IsNothing(szUID) Then
            szUidArray = szUID.Split(" ")
            If szUidArray.Length < UID_LENGTH Then
                MsgBox("書込む先は見つかりませんでした。")
                Return WriteError.NoTag
            End If
        Else
            MsgBox("書込む先は見つかりませんでした。")
            Return WriteError.NoTag
        End If

        For Each szByte As String In szUidArray
            i += 1
            dataSend(i) = CInt("&H" & szByte)
        Next

        If Not SetMod(Mode.Command) Then
            Return WriteError.TagReaderFailed
        End If

        ResetToReady()

        Dim rslt As WriteError = SendCommand(&H78, dataSend)
        If rslt <> WriteError.OK Then
            Return rslt
        End If

        If Not SetMod(Mode.RDLOOP) Then
            Return WriteError.TagReaderFailed
        End If

        'WaitToWrite()

        WriteData = WriteError.OK
    End Function

    Public Sub CheckTag(Optional ByVal fBeep As Boolean = True)
        'ResetToReady()
        g_SerialPort.Write({CChar("L"), CChar("O"), CChar("N"), CChar(Chr(CR))})
    End Sub

    Public Sub BeepWarning()
        Dim dataSend(1) As Byte
        dataSend(0) = &H0
        dataSend(1) = &H8

        SendCommand(&H42, dataSend)
    End Sub

    Private Sub ResetToReady()
        Dim dataSend(1) As Byte
        dataSend(0) = &H26
        dataSend(1) = &H40

        SendCommand(&H78, dataSend)
    End Sub

    Private Sub StayQuiet()
        Dim dataSend(1) As Byte
        dataSend(0) = &H2
        dataSend(1) = &H40

        SendCommand(&H78, dataSend)
    End Sub

    Public Sub WaitToWrite(Optional ByVal i As Integer = 120)
        'While serialPort.BytesToWrite > 0
        'End While
        System.Threading.Thread.Sleep(i)
        'IsWrited(0)
        'System.Threading.Thread.SpinWait(i * 1000)
    End Sub

    Public Function SendCommand(ByVal cmd1 As Byte, ByVal data As Byte(), Optional ByVal fIsRewrite As Boolean = False) As WriteError
        'Connect()
        'g_SerialPort.DiscardInBuffer()

        Dim command() As Byte
        ReDim command(data.Length + MIN_BYTE - 1)
        Dim i As Integer

        command(0) = &H2
        command(1) = &H0
        command(2) = cmd1
        If data.Length > &HFF Then
            command(3) = &HFF
        Else
            command(3) = data.Length
        End If
        For i = 0 To data.Length - 1
            command(i + 4) = data(i)
            If i = &HFF Then
                Exit For
            End If
        Next
        command(i + 4) = &H3
        command(i + 5) = GetCheckSum(command)
        command(i + 6) = &HD

        Try
            ''待ち時間設定
            'Dim iWaitTime As Integer = 100
            'Select Case cmd1
            '    Case &H4E 'SetMode
            '        If fIsEdit Then
            '            iWaitTime = 53
            '        Else
            '            iWaitTime = command.Length + 5
            '        End If
            '    Case &H78
            '        Select Case data(0)
            '            Case &H26 'ResetToReady
            '                If fIsEdit Then
            '                    iWaitTime = 43
            '                Else
            '                    iWaitTime = command.Length + 5
            '                End If
            '            Case &HA1 'WriteBytes
            '                iWaitTime = command.Length * 3.43 + 15
            '        End Select
            'End Select
            'シリアルポート送信
            g_fRespOK = False
            g_fNoTagResp = False
            g_fDataOK = False
            g_fDataSet = False
            g_fNACK = False
            g_SerialPort.Write(command, 0, command.Length)

            'レスポンス受信待ち
            Dim rslt As WriteError = WaitForResponse(command)
            If rslt <> WriteError.OK AndAlso Not fIsRewrite Then
                If Not fIsRewrite Then
                    'Debug.Print("Retry CMD=" & frmStartCompleteBase.BytesToHexString(command))
                    SendCommand = SendCommand(cmd1, data, True)
                Else
                    Return rslt
                End If
            ElseIf fIsRewrite Then
                Return rslt
            Else
                SendCommand = WriteError.OK
                'Debug.Print("ResponseOK=" & SendCommand.ToString)
            End If

            'ICタグデータ待ち
            If fIsRDLOOP Then
                'ResetToReady
                If cmd1 = &H78 And data(0) = &H26 Then
                    If g_fRespOK Then
                        Dim fRslt As Boolean = WaitForData(command.Length)
                        If fRslt Then
                            SendCommand = WriteError.OK
                        Else
                            SendCommand = WriteError.NoTag

                        End If
                    End If
                End If
            Else
                'SetToRDLOOP
                If cmd1 = &H4E And data(1) = &H58 Then
                    Dim fRslt As Boolean = WaitForData(command.Length)
                    If fRslt Then
                        SendCommand = WriteError.OK
                    Else
                        SendCommand = WriteError.NoTag

                    End If
                End If
            End If

        Catch ex As Exception
            SendCommand = WriteError.TagReaderFailed
            Throw ex
            'MsgBox(ex.Message)
        Finally
            While (IsNothing(byteRecv))
            End While
        End Try
    End Function

    'Private Event dr As SerialDataReceivedEventHandler

    'Private Sub WaitForResponse(ByVal iCmdLength As Integer)
    '    Dim timeBegin As DateTime = DateTime.Now
    '    While (serialPort1.BytesToRead > 0)
    '        Dim spanned As TimeSpan = DateTime.Now - timeBegin
    '        If spanned.TotalMilliseconds > iCmdLength * 10 Then
    '            'Dim ag As SerialDataReceivedEventArgs = CType(New EventArgs, SerialDataReceivedEventArgs)
    '            'RaiseEvent serialHandler(serialPort, ag)
    '            'Debug.Print("Failed.")
    '            Exit While
    '        End If
    '    End While
    'End Sub

    Private Function WaitForResponse(ByVal Cmd() As Byte) As WriteError
        Dim timeBegin As DateTime = DateTime.Now
        'Debug.Print("Command=" & frmStartCompleteBase.BytesToHexString(Cmd))
        While (Not g_fRespOK)
            'If Cmd(2) = &H78 And Cmd(4) = &H26 Then
            If g_fNoTagResp Then
                Return WriteError.NoTag
            End If
            'End If
            If g_fNACK Then
                Return WriteError.NACK
            End If
            WaitToWrite(50)
            Dim spanned As TimeSpan = DateTime.Now - timeBegin
            If spanned.TotalMilliseconds > 1000 Then
                'Debug.Print("TimeOut, ReadBuff=" & g_SerialPort.BytesToRead)
                DataRecive()
                'Debug.Print("ManualReadRslt=" & g_fRespOK.ToString)
                Exit While
            End If
        End While
        'Debug.Print("ResponseOK=" & g_fRespOK.ToString)

        If g_fRespOK Then
            Return WriteError.OK
        Else
            Return WriteError.TimeOut
        End If


        'WaitToWrite(1000)
        'If Not fRespOK Then

        'End If
    End Function

    Private Function WaitForData(ByVal iCmdLength As Integer) As Boolean
        Dim timeBegin As DateTime = DateTime.Now
        While (Not g_fDataOK)
            WaitToWrite(50)
            Dim spanned As TimeSpan = DateTime.Now - timeBegin
            If spanned.TotalMilliseconds > 1000 Then
                DataRecive()
                Exit While
            End If
        End While
        'If g_fDataOK Then
        '    While (Not g_fDataSet)
        '        Dim spanned As TimeSpan = DateTime.Now - timeBegin
        '        If spanned.TotalMilliseconds > 1000 Then
        '            Exit While
        '        End If
        '    End While
        'End If
        Return g_fDataOK
    End Function

    Private Function IsRecieving() As Boolean
        Return g_SerialPort.BytesToRead <= 0
    End Function

    Private Function GetCheckSum(ByVal command As Byte()) As Byte
        Dim index As Integer = command.Length - 2
        Dim i As Integer
        Dim lngCheckSum As Long = 0
        For i = 0 To index - 1
            lngCheckSum += CInt(command(i))
        Next
        GetCheckSum = CByte(lngCheckSum And CLng(&HFF))
    End Function

    Private Sub TimerWrite_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles TimerWrite.Tick
        Select Case WriteCond
            Case WriteFlow.SendCmd
            Case WriteFlow.WaitForRsp
            Case WriteFlow.WaitForData
            Case WriteFlow.Done
                WriteCond = WriteFlow.SendCmd
                TimerWrite.Stop()
        End Select
    End Sub
End Class